package common;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;


public class Game implements Serializable
{
	/**
     * 
     */
    private static final long serialVersionUID = -8422353014086869562L;
	private final String HallOfFameClientFileName = "chof.aqr";
	private final String undoFileName = "undoGame.aqr";
	private final String redoFileName = "redoGame.aqr";
	
	private int tilesPerPlayer = 6;
	private int initialCash = 6000;
	
    List<Tile> tileList = new ArrayList<Tile>();
    Board board;
    Company[] companies = new Company[7];
    Player[] players;
    String[] playerNames;
    Map<Integer,List<Player>> finalStandings = null;
    int currentPlayer = 0;
    private boolean beginningOfTurn = true;
    private boolean unDone = true;
    private boolean localGame = false;
    private int skippedTurns = 0;
    
    public Game(List<String> names, List<IUI> Uis, boolean localGame) 
    {
    	try {
    		this.localGame = localGame;
            init(names,Uis);
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public Map<Integer, List<Player>> getFinalStandings() {
		return finalStandings;
	}

	public int getTilesPerPlayer() {
    	return tilesPerPlayer;
    }
    
    public int getInitialCash() {
    	return initialCash;
    }
     
    boolean setUndo() throws RemoteException {
    	setUndone(false);
        return saveGame(undoFileName);
    }
    
    private void setUndone(boolean unDone) throws RemoteException {
    	this.unDone = unDone;
    	for (int i = 0; i < players.length; i++) {
    		players[i].getUI().setUndone(unDone);	
		}
    }
    
    public boolean isBeginningOfTurn() {
        return beginningOfTurn;
    }
    
    public boolean isUnDone() {
        return unDone;
    }
    
    private boolean saveGame(String fileName) {
        try {
            save(this, fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;    
    }
    
    public boolean saveGame() throws RemoteException {
    	String saveFileName = players[currentPlayer].getUI().getSaveFileName();
    	if (saveFileName == null) return false;
    	return saveGame(saveFileName);	
    }
    
    public boolean undo() throws RemoteException {
        if (!isUnDone()) {
            saveGame(redoFileName);
            boolean result = loadGame(undoFileName);
            setUndone(true);
            return result;
        }
        return false;
    }
    
    public boolean redo() throws RemoteException {
        if (isUnDone()) {
            saveGame(undoFileName);
            boolean result = loadGame(redoFileName);
            setUndone(false);
            return result;
        }
        return false;
    }
    
    
    private boolean loadGame(String fileName) {
        Game g;
        try {
            g = (Game) load(fileName);
            IUI ui = players[0].getUI();
            this.board = g.board;
            this.players = g.players;
            this.playerNames = g.playerNames;
            this.companies = g.companies;
            this.tileList = g.tileList;
            this.currentPlayer = g.currentPlayer;
            this.beginningOfTurn = g.beginningOfTurn;
            this.unDone = g.unDone;
            this.localGame = g.localGame;
            this.skippedTurns = g.skippedTurns;
            this.tilesPerPlayer = g.getTilesPerPlayer();
            this.initialCash = g.getInitialCash();
            // ONLY LOCAL!!!!:
            for (int i = 0; i < players.length; i++) {
            	players[i].setUI(ui);	
			}
            players[currentPlayer].getUI().setTurnInfo(board, players[currentPlayer]);	
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;    
    }
    
    public boolean loadGame() throws RemoteException {
    	String loadFileName = players[currentPlayer].getUI().getLoadFileName();
    	if (loadFileName == null) return false;
    	return loadGame(loadFileName);
    }
    
	public static void save(Object game, String filename)throws IOException {
	    FileOutputStream out = new FileOutputStream(filename);
	    ObjectOutputStream s = new ObjectOutputStream(out);
	    s.writeObject(game);
	    s.close();
	}
	
	public static Object load(String filename)throws Exception {
		FileInputStream f_in = new FileInputStream(filename);
		ObjectInputStream obj_in = new ObjectInputStream (f_in);
		Object obj = obj_in.readObject();
		obj_in.close();
        return obj;	
	}
    
    //Create tiles, init companies, create players & init their money, etc.
    private void init(List<String> names, List<IUI> Uis)  throws RemoteException
    {
    	int numOfPlayers = names.size();
    	
        companies[0] = new CheapCompany(enumCompany.SACKSON); 
        companies[1] = new CheapCompany(enumCompany.ZETA); 
        companies[2] = new MediumCompany(enumCompany.HYDRA); 
        companies[3] = new MediumCompany(enumCompany.FUSION); 
        companies[4] = new MediumCompany(enumCompany.AMERICA); 
        companies[5] = new ExpensiveCompany(enumCompany.PHOENIX); 
        companies[6] = new ExpensiveCompany(enumCompany.QUANTUM); 

        for(int i=0; i<9; i++)
        {
            for(int j=0; j<12; j++)
        	{
                tileList.add(new Tile(new Coordinate(i,j)));
            }
        }
        
        //int num;
        board = new Board(tileList);  
        //UI = new ConsoleUI(board, this);
        
        //UI.printWelcomeScreen();
        
        //pick personal rules
        //int[] definitions = UI.getUserGameDefinitions();
       // initialCash = definitions[0];
      //  tilesPerPlayer = definitions[1];
        
      //  do 
       // {
           // num = UI.getNumberOfPlayers();
           // if (num < 2 || num > 6)
            //{
             //   UI.printMessage("Please enter a number between 2-6!"); 
           // }
       // }
       // while (num < 2 || num > 6);
        players = new Player[numOfPlayers];     
        for (int i=0; i<numOfPlayers; i++)
        {
            players[i] = null;
        }
        
        //get players names        
        int downCounter = numOfPlayers;
        String currentName;
        Random generator = new Random();
        int currentIndex = 0;
        int playerIndex;
        do 
        {
            playerIndex = generator.nextInt(players.length);
            if (players[playerIndex] == null) 
            {
                currentName = names.get(currentIndex);
                players[playerIndex] = new Player(currentName, Uis.get(currentIndex));
           //     players[playerIndex].getUI().printWelcomeScreen();
                players[playerIndex].setIndex(playerIndex);
                players[playerIndex].increaseCash(initialCash);
                downCounter--;
                currentIndex++;
            } 

        }
        
        while(downCounter > 0);
        playerNames = new String[numOfPlayers];
        for(int i = 0; i < numOfPlayers; i++){
        	playerNames[i] = players[i].getName();
        }
        for(int i = 0; i < numOfPlayers; i++){
        	players[i].getUI().setPlayersName(playerNames);
        }
                
                
        placeStartTiles();
        
        for (int i=0; i < numOfPlayers; i++) {
            for (int j=0; j < tilesPerPlayer; j++) {
                players[i].addTile(getNewTile());
            }
        }
        
    }
    
    private void placeStartTiles() 
    {        
        for (int i=0; i<players.length; i++)
        {   
            Tile newTile = getNewTile();
            while (isCreateCompany(newTile))
            {
            	tileList.add(newTile);
            	newTile = getNewTile();
            }
            
            newTile.setOnBoard();
        }
    }
    
    private Tile getNewTile()
    {
        Random generator = new Random();
        Tile newTile = null;
        if (tileList.size() > 0)
        {
            newTile = tileList.get(generator.nextInt(tileList.size()));
            tileList.remove(newTile);
        }
        return newTile;
    }
    
    private boolean isCreateCompany(Tile tile)
    { 
        boolean foundNonCompanyNeighbor = false;        
        Tile[] Neighbors = board.getNeighbors(tile);        
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{
        		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null)
	            {
	            	return false;
	            }
        		
	            foundNonCompanyNeighbor = foundNonCompanyNeighbor || (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() == null);	            
        	}
        }
               
        return (foundNonCompanyNeighbor);        
    }
    
    private boolean isTileBetweenSafeCompanies(Tile tile) {                
        boolean foundFirstSafeCompanyNeighbor = false;        
        enumCompany firstSafeCompany = null;             
        Tile[] Neighbors = board.getNeighbors(tile);
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{        		
        		if (foundFirstSafeCompanyNeighbor)
	            {
        			if ((Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null && companies[Neighbors[i].getCompany().getIndex()].isSafe()
        				&& Neighbors[i].getCompany() != firstSafeCompany))
					{
        					return true;
					}
	            }
        		else
        		{
        			foundFirstSafeCompanyNeighbor = (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null && companies[Neighbors[i].getCompany().getIndex()].isSafe());
        			if (foundFirstSafeCompanyNeighbor)
		            {
		            	firstSafeCompany = Neighbors[i].getCompany();
		            }
        		}	            
        	}
        }
        
        return false;        
    }
    
    private Tile managePlaceTile()  throws RemoteException
    {
        Tile chosenTile = null;
        Coordinate chosenTileCoordinate;
        boolean tileCreateCompanyWhenAllOnBoard = false;
        boolean tileBetweenSafeCompanies = false;        
      
        //this is just to flag whether we should increase skippedTurns or not -
        //if the player placed a tile between safe companies, and there are tiles in the bank,
        //he'll get a new tile so maybe he'll have a tile to place in the next round..
        boolean increaseSkipped = true;
        boolean firstTileBetweenSafeCompanies = true; //this is also for the same purpose..
        
        do
        {
        	if (!makeCertainPlayerHasAMove(players[currentPlayer]))
        	{
        		if (increaseSkipped)
        		{
        			skippedTurns++;
        		}        		
        		players[currentPlayer].getUI().printMessage(1);        		
        		break;
        	}
        	
        	chosenTileCoordinate = players[currentPlayer].getUI().printPickTileDialog();
        	
        	// If an option was picked:
        	if (players[currentPlayer].getUI().getOptionMade()) {
        		return null;
        	}
        	
        	if (localGame) {
        		setUndo();
        	}
        	
        	chosenTile = board.getTile(chosenTileCoordinate);
	                
	        tileCreateCompanyWhenAllOnBoard = (isCreateCompany(chosenTile) && board.numOfCompaniesOnBoard() == companies.length);
	        if (tileCreateCompanyWhenAllOnBoard)
	        {
	        	players[currentPlayer].getUI().printMessage(15);
	        }
	        else
	        {
		        tileBetweenSafeCompanies = isTileBetweenSafeCompanies(chosenTile);
		        
		        if (tileBetweenSafeCompanies)
		        {
		        	players[currentPlayer].removeTile(chosenTile);
		        	
		        	chosenTile.setOnBoard();
		        	chosenTile.setIsBetweenSafeCompanies();
		        	
		        	players[currentPlayer].getUI().printMessage(2);
		        	
		        	if (tileList.size() == 0 && firstTileBetweenSafeCompanies)
		        	{
		        		increaseSkipped = false;
		        	}
		        	
		        	firstTileBetweenSafeCompanies = false;
		        }
	        }
        }
        while (tileCreateCompanyWhenAllOnBoard || tileBetweenSafeCompanies);

        if (chosenTile != null)
        {
	        chosenTile.setOnBoard();
	        players[currentPlayer].removeTile(chosenTile);
        }
        else
        {
        	skippedTurns = 0;
        }
        
        return chosenTile;
    }
    
    //This function only checks if the player has a legal move - meaning that he has ANY tiles,
    //and that not all of his tiles create a company, when it's not allowed to create a company.    
    //notice: if some of the player's tiles are between safe companies, this function will return true.
    private boolean makeCertainPlayerHasAMove(Player player)
    {
    	if (board.numOfCompaniesOnBoard() < companies.length)
    	{
    		return true;
    	}
    	
    	boolean FoundPlaceableTile = false;
    	
    	for (Tile tile : player.getTiles())
    	{
    		if (!isCreateCompany(tile))
    		{
    			FoundPlaceableTile = true;
    			break;
    		}
    	}
    	
    	return FoundPlaceableTile;
    }
    
    private void connectTilesToCompany(Tile newlyPlacedTile)
    {   
        enumCompany neighborCompany = null;       
        int companySizeIncrement=0;
        Tile[] Neighbors = board.getNeighbors(newlyPlacedTile);
        
        for (int i=0; i < 4; i++)
        {
        	if (Neighbors[i] != null)
        	{
        		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() != null)
        		{
        			neighborCompany = Neighbors[i].getCompany();
        			break;
        		}
        	}
        }
        
        if (neighborCompany != null)
        {
        	newlyPlacedTile.setCompany(neighborCompany);
        	companySizeIncrement++;
        	
        	 for (int i=0; i < 4; i++)
             {
             	if (Neighbors[i] != null)
             	{
             		if (Neighbors[i].isOnBoard() && Neighbors[i].getCompany() == null)
             		{
             			Neighbors[i].setCompany(neighborCompany);
             			companySizeIncrement++;
             		}
             	}
             }
        	 
        	 companies[neighborCompany.getIndex()].increaseSize(companySizeIncrement);
        }
    }
    
    private boolean CreateCompany(Tile newlyPlacedTile, enumCompany createdCompany)
    {
    	companies[createdCompany.getIndex()].setCompanyOnBoard();
    	board.setCompanyOnBoard(enumCompany.values()[createdCompany.getIndex()], true);
    	
    	Tile[] Neighbors = board.getNeighbors(newlyPlacedTile);
    	
    	newlyPlacedTile.setCompany(createdCompany);
    	int newCompanySize = 1;
    	for (int i=0; i < 4; i++)
    	{
    		if (Neighbors[i] != null && Neighbors[i].isOnBoard())
    		{
    			Neighbors[i].setCompany(createdCompany);
    			newCompanySize++;
    		}
    	}
    	
    	companies[createdCompany.getIndex()].increaseSize(newCompanySize);
    	
    	if (companies[createdCompany.getIndex()].remainingStocksInBank() > 0)
    	{
	    	companies[createdCompany.getIndex()].decreaseStocksInBank(1);
	    	players[currentPlayer].increaseCompanyStock(createdCompany,1);
	    	return true;
    	}
    	
    	return false;
    }
    
    private void manageBuyStocks()  throws RemoteException
    {
    	/*verify transaction*/
    	boolean repeatBuyingProcess;
    	int[] stocksToBuy;
    	
    	do
    	{

    	    
    	    stocksToBuy = players[currentPlayer].getUI().printStocksToBuyDialog(companies);
	    	
	    	repeatBuyingProcess = false;
	    	int playerCash = players[currentPlayer].getCash();
	    	
			for (int i=0; i < 7; i++)
			{
				if (companies[i].remainingStocksInBank() < stocksToBuy[i])
				{
					repeatBuyingProcess = true;
					players[currentPlayer].getUI().printMessage(10,companies[i].remainingStocksInBank(),i);
				}
				
				if (stocksToBuy[i] > 0)
				{
					playerCash -= stocksToBuy[i] * companies[i].getStockValue();
				}
			}
			
			if (playerCash < 0)
			{
				repeatBuyingProcess = true;
				players[currentPlayer].getUI().printMessage(6,(-1*playerCash));
			}
    	}
    	while(repeatBuyingProcess);
    	
    	
    	/*execute transaction*/
    	int playerCashBeforeTransaction = players[currentPlayer].getCash();
    	boolean isNonTrivialTransaction = false;
    	
    	for (int i=0; i < 7; i++)
    	{
    		if (stocksToBuy[i] > 0)
    		{
    			isNonTrivialTransaction = true;
    			
    			players[currentPlayer].decreaseCash(stocksToBuy[i] * companies[i].getStockValue());
    			players[currentPlayer].increaseCompanyStock(enumCompany.values()[i], stocksToBuy[i]);
    			companies[i].decreaseStocksInBank(stocksToBuy[i]); 
    			
    			if (!localGame){
    				for (Player p : players){
    					if (p.getIndex() != currentPlayer){
    						p.getUI().printMessage(31, currentPlayer, stocksToBuy[i], i);
    					}
    				}
    			}
    		}
    	}
    	
    	if (isNonTrivialTransaction)
    	{
    		players[currentPlayer].getUI().printMessage(5,(playerCashBeforeTransaction - players[currentPlayer].getCash()));
    	}
    	else {
    		//prints to all player did not buy any stocks
    		if (!localGame){
				for (Player p : players){
					if (p.getIndex() != currentPlayer){
						p.getUI().printMessage(34, currentPlayer);
					}
				}
			}
    	}
    }
    
    /*this function checks whether the newlyPlacedTile is in between of two companies.
     *we assume the new tile is NOT between two-or-more SAFE companies.*/
    private boolean isCompanySell(Tile newlyPlacedTile)
    {
    	Tile[] neighbors = board.getNeighbors(newlyPlacedTile);
    	boolean foundFirstCompany = false;
    	enumCompany firstCompany = null;
    	
    	for (int i=0; i < 4; i++)
    	{
    		if (neighbors[i] != null)
    		{
    			if (foundFirstCompany)
    			{
    				if (neighbors[i].isOnBoard() && neighbors[i].getCompany() != null
    					&& neighbors[i].getCompany() != firstCompany)
    				{
    					return true;
    				}
    			}
    			else
    			{
	    			if (neighbors[i].isOnBoard() && neighbors[i].getCompany() != null)
	    			{
	    				foundFirstCompany = true;
	    				firstCompany = neighbors[i].getCompany();
	    			}
    			}
    		}
    	}
    	
    	return false;
    }
    
    private void manageCompanySell(Tile newlyPlacedTile)  throws RemoteException
    {
        Tile[] neighbors = board.getNeighbors(newlyPlacedTile);     
        Map<Integer, List<Company>> neighborCompanies = new HashMap<Integer, List<Company>>();            
        int numOfNeighborCompanies = 0;
        for (int i = 0; i < 4; i++)
        {
            if (neighbors[i] != null && neighbors[i].getCompany() != null)
            {
                Company curCompany = companies[neighbors[i].getCompany().getIndex()];
                int companySize = curCompany.getSize();
                
                if (!neighborCompanies.containsKey(companySize))
                {
                    List<Company> compOfSpecificSize = new ArrayList<Company>();
                    compOfSpecificSize.add(curCompany);
                    neighborCompanies.put(companySize,compOfSpecificSize);
                    numOfNeighborCompanies++;
                }
                else if (!neighborCompanies.get(companySize).contains(curCompany))
                {                        
                    neighborCompanies.get(companySize).add(curCompany);
                    numOfNeighborCompanies++;
                }
            }
        }

        neighborCompanies = new TreeMap<Integer, List<Company>>(neighborCompanies);
        Company buyingCompany = null;            

        for (Map.Entry<Integer, List<Company>> curEntry : neighborCompanies.entrySet())
        {  
            if (curEntry.getValue().size() > 1)
            {
                /*Call function where user sorts the List*/
            	players[currentPlayer].getUI().printSortBetweenSameSizeCompanies(curEntry.getValue(), curEntry.getKey());
            }

            buyingCompany = curEntry.getValue().get(curEntry.getValue().size()-1);
        }
        
        for (List<Company> specificSizeCompanySet : neighborCompanies.values())
        {
        	for (Company soldCompany : specificSizeCompanySet)
            {
        		if (soldCompany != buyingCompany)            
	            {                
	                sellCompanyToCompany(soldCompany, buyingCompany);
	            }
            }
        }

        //setting the placed tile to be part of the buying company
		newlyPlacedTile.setCompany(enumCompany.values()[buyingCompany.getCompanyIndex()]);
		buyingCompany.increaseSize(1);
		//setting any nearby tiles which also got connected by the placed tile to be part of the buying company
		for (int i = 0; i < 4; i++)
		{
			if (neighbors[i] != null && neighbors[i].isOnBoard() && neighbors[i].getCompany() == null)
            {
            	neighbors[i].setCompany(enumCompany.values()[buyingCompany.getCompanyIndex()]);
            	buyingCompany.increaseSize(1);
            }
		}
    } 
    
    private void sellCompanyToCompany(Company soldCompany, Company buyingCompany) throws RemoteException
    {    	    	   	
    	enumCompany soldCompanyEnum = enumCompany.values()[soldCompany.getCompanyIndex()];
    	enumCompany buyingCompanyEnum = null;    	
    	
    	//Calculate prizes.
    	List<Player> topShareHolders = new ArrayList<Player>();
    	List<Player> secondShareHolders = new ArrayList<Player>();
    	int mostShares = 0;
    	int secondMostShares = 0;
    	
    	for (Player player : players)
    	{
    		int numOfPlayerShares = player.getCompanyStocks(soldCompanyEnum);
    		
    		if (numOfPlayerShares > mostShares)
    		{
    			topShareHolders.clear();
    			topShareHolders.add(player);
    			mostShares = numOfPlayerShares;
    		}
    		
    		else if (numOfPlayerShares > 0 && numOfPlayerShares == mostShares)
    		{
    			topShareHolders.add(player);
    		}    		
    	}
    	
    	if (topShareHolders.size() < 2)
    	{
    		for (Player player : players)
        	{
    			if (!topShareHolders.contains(player))
    			{
	        		int numOfPlayerShares = player.getCompanyStocks(soldCompanyEnum);
	        		
	        		if (numOfPlayerShares > secondMostShares)
	        		{
	        			secondShareHolders.clear();
	        			secondShareHolders.add(player);
	        			secondMostShares = numOfPlayerShares;
	        		}
	        		
	        		else if (numOfPlayerShares > 0 && numOfPlayerShares == secondMostShares)
	        		{
	        			secondShareHolders.add(player);
	        		}    
    			}
        	}
    	}
    	
    	//Give out prizes.
    	int prizeMoney = 0;
    	int secondPrizeMoney = 0;
    	if (secondShareHolders.size() == 0) //occurs when either only 1 share holder, or when there is a tie for first place
    	{
    		prizeMoney = (soldCompany.getFirstPrizeValue() + soldCompany.getSecondPrizeValue()) / topShareHolders.size();
    		 
    		for (Player player : topShareHolders)
    		{
    			player.increaseCash(prizeMoney);
    		}
    	}    	
    	else
    	{
    		prizeMoney = soldCompany.getFirstPrizeValue() / topShareHolders.size();
    		
    		topShareHolders.get(0).increaseCash(soldCompany.getFirstPrizeValue());
    		
    		secondPrizeMoney = soldCompany.getSecondPrizeValue() / secondShareHolders.size();
   		 
    		for (Player player : secondShareHolders)
    		{
    			player.increaseCash(secondPrizeMoney);
    		}
    	}
    	
    	//needs to print "company A sold to company B", all players' stocks of company A, and who gets prizes and how much..
    	//also needs to check if buyingCompany is null, if so then print "selling to bank" or something..
    	printSellCompanyDialog(soldCompany, buyingCompany, topShareHolders, 
                secondShareHolders, prizeMoney, secondPrizeMoney, players);
    	
    	
    	int numOfSharesToSell, numOfSharesToConvert;
    	int playerIndex = currentPlayer;    	
    	boolean firstTime = true;
    	
    	if (buyingCompany != null) //(buyingCompany == null) occurs at end-game.
    	{
    		buyingCompanyEnum = enumCompany.values()[buyingCompany.getCompanyIndex()];
    		
    		//go over all players, starting with the one who caused the merger, then continplayers[currentPlayer].getUI()ng "clock-wise". stop upon returning to merger-causer.
	    	while (firstTime || (playerIndex != currentPlayer))
	    	{
	    		firstTime = false;
	    		int curPlayerShares = players[playerIndex].getCompanyStocks(soldCompanyEnum);
	    		
	    		if (curPlayerShares > 0) //if the player has shares of the sold company
	    		{
	    			//Sell soldCompany shares
		    		do
		    		{
		    			players[playerIndex].getUI().setTurnInfo(board, players[playerIndex]);
		    			numOfSharesToSell = players[playerIndex].getUI().printSellCompanySell(soldCompany); //should also announce price per stock.
		    			if (numOfSharesToSell > curPlayerShares || numOfSharesToSell < 0)
		    			{
		    				players[playerIndex].getUI().printMessage(7,curPlayerShares);
		    			}
		    		}
		    		while (numOfSharesToSell > curPlayerShares || numOfSharesToSell < 0);
		    		
		    		if (numOfSharesToSell > 0)
		    		{
			    		int moneyIncrease = numOfSharesToSell * soldCompany.getStockValue();
			    		players[playerIndex].decreaseCompanyStock(soldCompanyEnum, numOfSharesToSell);
			    		players[playerIndex].increaseCash(moneyIncrease);
			    		if (!localGame) {  // Not happening in local version:
			    			for (Player p : players){
			    				p.getUI().printMessage(11,playerIndex, numOfSharesToSell, moneyIncrease);	    		
			    			}
			    		}
		    			curPlayerShares -= numOfSharesToSell;
		    		    soldCompany.increaseStocksInBank(numOfSharesToSell);
		    		}
		    		
		    		//Convert soldCompany shares
		    		if (curPlayerShares > 0)
		    		{
			    		do
			    		{
			    			players[playerIndex].getUI().setTurnInfo(players[playerIndex]);
			    			numOfSharesToConvert = players[playerIndex].getUI().printSellCompanyChange(soldCompany, buyingCompany);
			    			if (numOfSharesToConvert > curPlayerShares || numOfSharesToConvert < 0 || numOfSharesToConvert%2 != 0)
			    			{
			    				players[playerIndex].getUI().printMessage(8,curPlayerShares);
			    			}
			    		}
			    		while (numOfSharesToConvert > curPlayerShares || numOfSharesToConvert < 0 || numOfSharesToConvert%2 != 0);
			    		
			    		if (numOfSharesToConvert > 0)
			    		{
				    		players[playerIndex].decreaseCompanyStock(soldCompanyEnum, numOfSharesToConvert);
				    		players[playerIndex].increaseCompanyStock(buyingCompanyEnum, numOfSharesToConvert/2);
				    		if (!localGame) {
					    		for (Player p : players){
					    			p.getUI().printMessage(12,playerIndex, numOfSharesToConvert, numOfSharesToConvert/2, buyingCompany.getCompanyIndex());
					    		}
				    		}
				    		curPlayerShares -= numOfSharesToConvert;
				    		buyingCompany.decreaseStocksInBank(numOfSharesToConvert/2);
				    		soldCompany.increaseStocksInBank(numOfSharesToConvert);
			    		}
		    		}
		    		if (!localGame) {
			    		for (Player p : players){
			    			p.getUI().printMessage(13, playerIndex, curPlayerShares);
			    		}
		    		}
		    	}
	    		
	    		playerIndex++;
	    		playerIndex %= players.length;
	    	}
    	}
    	else //means we're at end-game.
    	{
    		//go over all players and sell all of their soldCompany shares.
    		for (int i = 0; i < players.length; i++)
	    	{
	    		int curPlayerShares = players[i].getCompanyStocks(soldCompanyEnum);
	    		
	    		if (curPlayerShares > 0) //if the player has shares of the sold company
	    		{	
		    		int moneyIncrease = curPlayerShares * soldCompany.getStockValue();
		    		players[i].decreaseCompanyStock(soldCompanyEnum, curPlayerShares);
		    		players[i].increaseCash(moneyIncrease);
		    		try {
		    		    players[currentPlayer].getUI().printMessage(14,i, curPlayerShares, moneyIncrease);
		    		} catch (RemoteException re) {
		    		    System.err.println("Error while calling " + players[currentPlayer].getName() + " UI");
		    		}
	    		}
	    	}
    	}
    	
        
        //go over ALL TILES ON BOARD and set the ones who are soldCompany to be buyingCompany.
    	for (int i=0; i < 9; i++)
    	{
    		for (int j=0; j < 12; j++)
    		{
    			Tile tile = board.getTile(i,j);
    			if (tile.getCompany() == soldCompanyEnum)
    			{
    				tile.setCompany(buyingCompanyEnum);
    			}
    		}    		
    	}

        if (buyingCompany != null)
        {
           buyingCompany.increaseSize(soldCompany.getSize());
        }
        
        soldCompany.removeFromBoard();
        board.setCompanyOnBoard(soldCompanyEnum, false);	
	}

	public void startGame() 
    {
            try {
                
                while(!isGameOver()) 
                {          
                	if (localGame) {
                		players[currentPlayer].getUI().printMessage(29,currentPlayer);
                	}
                	if (!localGame) {
                		for (Player p : players){
                			if (currentPlayer!=p.getIndex()){
                			p.getUI().printMessage(30, currentPlayer);
                			}
                		}
                	}
                	
                	players[currentPlayer].getUI().setTurnInfo(board, players[currentPlayer]);
      
                    players[currentPlayer].getUI().printBoard();
                    
                    beginningOfTurn = true;
                    players[currentPlayer].getUI().setBeginningOfTurn(true);
                    
                    Tile newlyPlacedTile = managePlaceTile();
                    
                    // Only in local game options:
                    if (localGame) {
                    	// check if any option was picked:
                    	while (players[currentPlayer].getUI().getOptionMade()) {
                    		if (players[currentPlayer].getUI().getEndGame()) {
                    			break;
                    		}
                    		if (players[currentPlayer].getUI().getLoadGame()) {
                    			loadGame();
                    		}
                    		if (players[currentPlayer].getUI().getSaveGame()) {
                    			saveGame();
                    		}
                    		if (players[currentPlayer].getUI().getUndoGame()) {
                    			undo();
                    		}
                    		if (players[currentPlayer].getUI().getRedoGame()) {
                    			redo();         	
                    		}
                    		players[currentPlayer].getUI().resetOptions();
                    		newlyPlacedTile = managePlaceTile();
                    	}
                    	players[currentPlayer].getUI().setFirstTurn();
                    }
                    
                	// Quit;
                    if (players[currentPlayer].getUI().getEndGame()) {
                    	break;
                    }
                    
                    beginningOfTurn = false;
                    players[currentPlayer].getUI().setBeginningOfTurn(false);
                    
                    if (newlyPlacedTile != null)
                    {
                    	enumCompany createdCompany = null;
                        if (isCreateCompany(newlyPlacedTile))
                        {
                        	
                        	do{
                        	createdCompany = players[currentPlayer].getUI().printChooseCompanyDialog(companies);	
                        	}
                        	while (companies[createdCompany.getIndex()].isOnBoard());
                        	
                        	if (!CreateCompany(newlyPlacedTile, createdCompany))
                        	{
                        		players[currentPlayer].getUI().printMessage(3);
                        	}
                        }
                        

                        else if(isCompanySell(newlyPlacedTile))
                        {
                        	manageCompanySell(newlyPlacedTile);
                        }
                        
                        else
                        {
                        	connectTilesToCompany(newlyPlacedTile);
                        }
                        if (!localGame){ 
                         	 for (Player p : players){
                         		 if (p.getIndex() != currentPlayer){
                         			 p.getUI().setTurnInfo(board, players[currentPlayer]);
                         			 p.getUI().printMessage(32, currentPlayer, newlyPlacedTile.getLocation().getX(), newlyPlacedTile.getLocation().getY());
                         			 if (createdCompany != null){
                     	    			p.getUI().printMessage(35,createdCompany.getIndex());
                     	    			}
                         			 p.getUI().printBoard();
                         		 }
                         	 }
                        }
                    } 
                    else if (!localGame){
                      	 for (Player p : players){
                      		if (p.getIndex() != currentPlayer){
                           	p.getUI().printMessage(33, currentPlayer);
                      		}
                      	 }
                    }
                    
                                
                    if (board.numOfCompaniesOnBoard() > 0)
                    {
                        players[currentPlayer].getUI().setTurnInfo(board, players[currentPlayer]);
                        manageBuyStocks();
                    }
      
                    while (players[currentPlayer].getTiles().size() < tilesPerPlayer)
                    {
                    	Tile newTile = getNewTile();
                    	if (newTile != null)
                    	{
                    		players[currentPlayer].addTile(newTile); //was getNewTile()
                    	}
                    	else
                    	{
                    		players[currentPlayer].getUI().printMessage(4);
                    		break;
                    	}
                    }
                    
                   // players[currentPlayer].getUI().printMessage(9);
                    
                    currentPlayer++;
                    currentPlayer %= players.length;    


                }
   
                endGame();
	      
            } catch (UnmarshalException ue) {
                /* This means users has quit unexpectedly */
                try {
                    endGame();
                } catch (RemoteException e) {
                    System.err.println("What?!");
                    e.printStackTrace();
                }
                
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }        
    }
    
    @SuppressWarnings("unchecked")
	private void calcAndPrintEndGameResults() throws RemoteException
    {
        ReverseIntegerComparator comparator = new ReverseIntegerComparator();
        
    	finalStandings = new TreeMap<Integer,List<Player>>(comparator);
    	Map<Integer,List<String>> hof = null;
    	int minimumForHOF = 0;
    	int index;
    	String HallOfFameFileName = HallOfFameClientFileName;
    	
    	if (localGame) {
        	// check if HallOfFameFile exists:
        	File HallOfFameFile = new File(HallOfFameFileName);
        	if (HallOfFameFile.exists()) {
                try {
        			hof = (Map<Integer,List<String>>) load(HallOfFameFileName);
        		} catch (Exception e) {			
        			e.printStackTrace();
        		}
        	} else {
        		hof = new TreeMap<Integer,List<String>>(comparator);
        	}
        	
    		//finding the minimum cash needed to enter the Hall of Fame
        	// note that player's cash needs to be bigger than minimumForHOF
    		index = 1;
    		for (int cash : hof.keySet()) {
    			index += hof.get(cash).size();
    			if (index > 10) {
    				minimumForHOF = cash;
    				break;
    			}		
    		}
    	}
    		
        for (Player player : players) {
        	int curPlayerCash = player.getCash();
        	
        	if (finalStandings.containsKey(curPlayerCash)) {
        		finalStandings.get(curPlayerCash).add(player);
        	} else {
        		List<Player> sameCashList = new ArrayList<Player>();
        		sameCashList.add(player);
        		finalStandings.put(curPlayerCash, sameCashList);
        	}
        	
        	if (localGame) {
            	//check insertions to Hall of Fame
            	if (minimumForHOF < curPlayerCash) {
            		if (hof.containsKey(curPlayerCash)) {
            			hof.get(curPlayerCash).add(player.getName());
            		} else if (player.IsHuman()) {
            			List<String> sameCashPlayers = new ArrayList<String>();
            			sameCashPlayers.add(player.getName());
            			hof.put(curPlayerCash, sameCashPlayers);
            		}
            	}
        	}
        }
        if (!localGame) {  // Online game:
	        for (int k = 0; k < players.length; k++ ) { // final results printed to all
	        	try {
	                players[k].getUI().printEndGameResults(finalStandings);
	        	} catch (RemoteException re) {
	        	    System.err.println("Error while calling " + players[k].getName() + " UI");
	        	}
	        }
        } else { //Local game:
        	players[0].getUI().printEndGameResults(finalStandings);
        }
        
        if (localGame) {
            //remove anything except for the top 10 results from the HoF
            // note: hof is sorted from biggest to smallest.
            index = 0;
            Map<Integer,List<String>> newHOF = new TreeMap<Integer,List<String>>(comparator);
            for (int cash : hof.keySet()) {
                if (index + hof.get(cash).size() <= 10) {
                    index += hof.get(cash).size();
                    newHOF.put(cash, hof.get(cash));
                } else {
                    List<String> currentCashPlayers = new ArrayList<String>();
                    for (int i=0; index < 10 && i < hof.get(cash).size(); i++, index++) {
                        currentCashPlayers.add(hof.get(cash).get(i));
                    }
                    newHOF.put(cash, currentCashPlayers);
                    break;
                }
            }
            hof = newHOF;   
            players[0].getUI().printHoF(hof);
            try {
                save(hof, HallOfFameFileName);
            } catch (IOException e) {           
                e.printStackTrace();
            }
        }
    }

    private void sellAllRemainingCompanies()  throws RemoteException
    {
    	//go over all companies and sell all the ones on board to the bank.
        for (Company company : companies)
        {
        	if (company.isOnBoard())
        	{
        		sellCompanyToCompany(company, null);
        	}
        }        
    }

    //checks for 3 11+ companies, 1 41+ company.
    private boolean isGameOver()
    {
        if (skippedTurns == players.length)
        {
        	return true;
        }
    	
    	int counter = 0;                
        
        for (int i=0; i < 7; i++) {
            if (companies[i].getSize() >= 41) {
                return true;
            } else if (companies[i].getSize() >= 11) {
                counter++;
            }
        }
        
        if (counter >= 3) {
            return true;
        }
        
        return false;
    }
    
    public void printSellCompanyDialog(Company soldCompany, Company buyingCompany, List<Player> topShareHolders, 
            List<Player> secondShareHolders, int prizeMoney, int secondprizeMoney, Player[] players)
    throws RemoteException{
        
        ShareSizeComparator comparator = new ShareSizeComparator(enumCompany.values()[soldCompany.getCompanyIndex()]);
        
        int length;
        if (!localGame) { // Online Game:
        	length = players.length;
        } else { // Local Game:
        	length = 1;
        }
        
        for (int i=0; i<length; i++) {
            try {
                players[i].getUI().clearScreen();
            
            	if (buyingCompany == null) {
            		players[i].getUI().printMessage(16, soldCompany.getCompanyIndex());
            	} else {
            		players[i].getUI().printMessage(17, soldCompany.getCompanyIndex(), buyingCompany.getCompanyIndex());
            	}
             
            	players[i].getUI().printMessage(18);
            	for (Player shareHolder : topShareHolders) {
            		players[i].getUI().printMessage(19, shareHolder.getIndex(), prizeMoney);
            	}
            
            	if (secondShareHolders.size() > 0) {
            		players[i].getUI().printMessage(20);  
            		for (Player shareHolder : secondShareHolders) {
            			players[i].getUI().printMessage(21, shareHolder.getIndex(),secondprizeMoney);
            		}
            	}
            } catch (RemoteException re) {
                System.err.println("Error while calling " + players[i].getName() + " UI");
            }
    	}
        int counter = 0;
        for (int i=0; i<players.length; i++) {
            if (players[i].getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()]) > 0) {
                counter++;
            }
        }
        
        Player shareHolders[] = new Player[counter];
        
        int j = 0;
        for (int i=0; i<players.length; i++) {
            if (players[i].getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()]) > 0) {
                shareHolders[j] = players[i];
                j++;
            }
        }
        
        Arrays.sort(shareHolders, comparator);
        for (int i=0; i<length; i++) {
            try {
            	players[i].getUI().printMessage(22);
            	for (Player shareHolder : shareHolders) {
            		players[i].getUI().printMessage(23,shareHolder.getIndex(),
                		shareHolder.getCompanyStocks(enumCompany.values()[soldCompany.getCompanyIndex()])
                        ,soldCompany.getCompanyIndex());
            	}
            } catch (RemoteException re) {
                System.err.println("Error while calling " + players[i].getName() + " UI");
            }
        }
        
    }
    
    class ShareSizeComparator implements Comparator<Player> {

        enumCompany company;
        
        public ShareSizeComparator(enumCompany company) {
            this.company = company;
        }
        
        public int compare(Player player1, Player player2) {
            return player2.getCompanyStocks(company) - player1.getCompanyStocks(company);
        }
    }
    
    void endGame() throws RemoteException  {
    	  sellAllRemainingCompanies();
          calcAndPrintEndGameResults();
          
          if (localGame) {
              
              File undo = new File(undoFileName);
              File redo = new File(redoFileName);
              
              if (undo.exists()) {
                  undo.delete();
              }
              
              if (redo.exists()) {
                  redo.delete();
              }
              
          }
          
    }
   
}